#include "CodeGenerator.h"

#include <QListIterator>
#include <QFile>
#include <QTextStream>
#include <QHashIterator>

#include "XmlParser.h"
#include "BBClass.h"
#include "Validator.h"
#include "Util.h"

CodeGenerator* CodeGenerator::m_instance = NULL;


CodeGenerator* CodeGenerator::instance()
{
	if ( m_instance == NULL)
	{
		m_instance = new CodeGenerator();
	}

	return m_instance;
}
																
void CodeGenerator::generateClasses( QString xmlFilePath, QString classesDestiny )
{
	XmlParser* parser = XmlParser::instance();

	if ( parser == NULL )
	{
		//TODO:	ABORT !!!
	}

	QListIterator<BBClass> i( *parser->getUserClasses( xmlFilePath ) );

	//Get all classes
	while( i.hasNext() )
	{
		BBClass currentClass = i.next();

		//Create the class header file
		createClassHeaderFile( currentClass, classesDestiny );

		//Create the class source file
		createClassSourceFile( currentClass, classesDestiny );

		//Generate the <className>Bear header file
		createClassBearHeaderFile( currentClass, classesDestiny );

		//Generate the <className>Bear source file
		createClassBearSourceFile( currentClass, classesDestiny );
	}
}

void CodeGenerator::createClassHeaderFile( BBClass& userClass, QString classesDestiny )
{
	QString className = userClass.getName();
	QFile classFile( QString( "%1%2%3%4" )
					 .arg( classesDestiny ) 
					 .arg( "\\" )
					 .arg( className )
					 .arg( ".h" ) );

	if( !classFile.open( QIODevice::WriteOnly ) )
	{
		//TODO: ABORT !!!
 		return;
	}

	QString classBody = QString( "%1%2%3%4%5%6%7%8%9%10%11%12%13" )
		.arg( "#ifndef " + convertToUppercase( className ) + "_H \n" )
		.arg( "#define " + convertToUppercase( className ) + "_H \n\n" )
		//Includes
		.arg( "#include \"BearFactory.h\"\n" )
		.arg( "#include \"" + className + "Bear.h" + "\"\n\n\n" )
		//Class declaration
		.arg( "class " + className + "\n" )
		.arg( "     : public " + className + "Bear\n" )
		.arg( "{\n" )
		.arg( "public:\n" )
		//Constructor
		.arg( "    " + className + "();\n\n" )
		//Destructor
		.arg( "    ~" + className + "();\n\n" )
		.arg( "    //You can edit this part - Start\n\n" )
		.arg( "    //You can edit this part - End\n" )
		//End class declaration
		.arg( "};\n\n\n" );

	QString classFactory = QString( "%1%2%3%4%5%6%7%8%9%10%11%12%13%14%15%16%17" )
		//Class declaration
		.arg( "class " + className + "Factory\n" )
		.arg( "     : public BBFactory\n" )
		.arg( "{\n" )
		.arg( "public:\n" )
		//Constructor
		.arg( "    " + className + "Factory()\n" )
		.arg( "    {\n" )
		.arg( "        BearFactory::instance()->registerFactory( \"" + className + "\", this );\n" )
		.arg( "    }\n\n" )
		//Destructor
		.arg( "    ~" + className + "Factory()\n" )
		.arg( "    {\n" )
		.arg( "        //DOES NOTHING\n" )
		.arg( "    }\n\n" )
		.arg( "    virtual Bear* instance() const\n" )
		.arg( "    {\n" )
		.arg( "        return new " + className + "();\n" )		.arg( "    }\n" )
		.arg( "};\n\n" );

	classBody = QString( "%1%2%3%4" )
		.arg( classBody )
		.arg( classFactory )
		.arg( "Q_DECLARE_METATYPE( " + className + " );\n\n" )
		.arg( "#endif\n" );

	QTextStream ts( &classFile );
	ts << classBody;

	classFile.close();
}

void CodeGenerator::createClassSourceFile( BBClass& userClass, QString classesDestiny )
{
	QString className = userClass.getName();

	QFile classFile( QString( "%1%2%3%4" )
		.arg( classesDestiny ) 
		.arg( "\\" )
		.arg( className )
		.arg( ".cpp" ) );

	if( !classFile.open( QIODevice::WriteOnly ) )
	{
		//TODO: ABORT !!!
		return;
	}

	QString classBegin = QString( "%1%2" )
		.arg( "#include \"" + className + ".h\"\n\n" )
		.arg( "CREATE_FACTORY( " + className + " );\n\n\n" );

	QString classConstructor = QString( "%1%2%3%4%5" )
		.arg( className + "::" + className + "()\n" )
		.arg( "{\n" )
		.arg( "    //You can edit this part - Start\n\n" )
		.arg( "    //You can edit this part - End\n" )
		.arg( "}\n\n" );

	QString classDestructor = QString( "%1%2%3%4%5" )
		.arg( className + "::~" + className + "()\n" )
		.arg( "{\n" )
		.arg( "    //You can edit this part - Start\n\n" )
		.arg( "    //You can edit this part - End\n" )
		.arg( "}\n\n" );

	QString classEnd = QString( "%1%2" )
		.arg( "//You can edit this part - Start\n\n" )
		.arg( "//You can edit this part - End\n" );

	QTextStream ts( &classFile );
	ts << QString( "%1%2%3%4" )
		.arg( classBegin )
		.arg( classConstructor )
		.arg( classDestructor )
		.arg( classEnd );

	classFile.close();
}

void CodeGenerator::createClassBearHeaderFile( BBClass& userClass, QString classesDestiny )
{
	QString className = userClass.getName();
	QFile classFile( QString( "%1%2%3%4%5" )
		.arg( classesDestiny ) 
		.arg( "\\" )
		.arg( className )
		.arg( "Bear" )
		.arg( ".h" ) );

	if( !classFile.open( QIODevice::WriteOnly ) )
	{
		//TODO: ABORT !!!
		return;
	}

	QString classExtention = userClass.getExtension();
	
	//Check if class has a super class
	if( userClass.getExtension() == "" )
	{
		classExtention = "Bear";
	}

	QString classBody = QString( "%1%2%3%4%5%6%7%8%9%10%11%12%13" )
		.arg( "#ifndef " + convertToUppercase( className ) + "_BEAR_H \n" )
		.arg( "#define " + convertToUppercase( className ) + "_BEAR_H \n\n" )
		//Includes
		.arg( "#include \"Bear.h\"\n" )
		.arg( "#include \"" + classExtention + ".h\"\n\n\n" )
		//Class declaration
		.arg( "class " + className + "Bear\n" )
		.arg( "     : public " + classExtention + "\n" )
		.arg( "{\n" )
		.arg( "public:\n" )
		//Constructor
		.arg( "    " + className + "Bear();\n\n" )
		//Destructor
		.arg( "    ~" + className + "Bear()\n" )
		.arg( "    {\n" )
		.arg( "        delete( m_object );\n" )
		.arg( "    }\n\n" );

	QHashIterator<QString, QString> i( userClass.getAttributesType() );

	//Get all attributes
	while ( i.hasNext() )
	{
		i.next();

		QString attributeName = i.key();
		QString attributeType = i.value();

		//Change the first character to uppercase ( software pattern get/set )
		QChar firstAttributeLetter = attributeName[0].toUpper();
		attributeName[0] = firstAttributeLetter;

		classBody.append( buildPrimitiveGetSetHeader( attributeName, attributeType ) );
	}

	QString classBodyEnd = QString( "%1%2%3%4" )
		.arg( "private:\n" )
		.arg( "    BBObject* m_object;\n" )
		.arg( "};\n\n" )
		.arg( "#endif" );

	classBody.append( classBodyEnd );

	QTextStream ts( &classFile );
	ts << classBody;

	classFile.close();
}

void CodeGenerator::createClassBearSourceFile( BBClass& userClass, QString classesDestiny )
{
	QString className = userClass.getName();
	QFile classFile( QString( "%1%2%3%4%5" )
		.arg( classesDestiny ) 
		.arg( "\\" )
		.arg( className )
		.arg( "Bear" )
		.arg( ".cpp" ) );

	if( !classFile.open( QIODevice::WriteOnly ) )
	{
		//TODO: ABORT !!!
		return;
	}

	QString classBegin( "#include \"" + className +".h\"\n\n" );
	QString classStaticVariablesName = "";
	QString classStaticVariablesType = "";
	QString classGetSetMethods = "";
	QString classConstructor = QString( "%1%2%3%4" )
		.arg( className + "Bear" + "::" + className + "Bear()\n" )
		.arg( "{\n" )
		.arg( "    m_object = new BBObject;\n" )
		.arg( "    m_object->setName( \"" + className + "\" );\n" );

	QHashIterator<QString, QString> i( userClass.getAttributesType() );

	//Get all attributes
	while ( i.hasNext() )
	{
		i.next();

		QString attributeName = i.key();
		QString attributeType = i.value();

		//This kinds of classes have static variables for the name and type of the class's attributes
		QString currentColumnName( "COLUMN_NAME_" + convertToUppercase( attributeName ) );
		QString currentColumnType( "COLUMN_TYPE_" + convertToUppercase( attributeName ) );

		//Add static variables in the class
		classStaticVariablesName.append( "QString " + currentColumnName + " = \"" + attributeName + "\";\n" );
		classStaticVariablesType.append( "QString " + currentColumnType + " = \"" + attributeType + "\";\n" );

		//Change the first character to uppercase ( software pattern get/set )
		QChar firstAttributeLetter = attributeName[0].toUpper();
		attributeName[0] = firstAttributeLetter;

		//Add the current attribute in constructor
		classConstructor = QString( "%1%2" )
			.arg( classConstructor )
			.arg( "    m_object->setAttributeType( " + currentColumnName + ", " + currentColumnType + " );\n" );

		Validator* validator = Validator::instance();
		
		//Check if attribute's type is primitive
		if ( validator->isPrimitiveType( attributeType ) )
		{
			classConstructor = QString( "%1%2" )
				.arg( classConstructor )
				.arg( "    m_object->setAttributePrimitiveValue( " + currentColumnName + ", NULL );\n" );

			//Create get/set methods for the current attribute
			classGetSetMethods.append( buildPrimitiveGetSetSource( className, attributeName, attributeType, currentColumnName ) );
		}
		//Check if attribute's type is a class
		else if ( validator->isCompositeType( attributeType ) )
		{
			//TODO: Caso quando o atributo é uma composição. Fazer depois
		}
		else
		{
			//TODO: ABORT! Senao nao for nenhum dos tipos, mesmo passando pelo validador antes, é um bug
		}
	}

	//Close the constructor with parenthesis
	classConstructor = QString( "%1%2%3" )
		.arg( classConstructor )
		.arg( "    addObject( m_object );\n" )
		.arg( "}\n\n" );

	classStaticVariablesName.append( "\n" );
	classStaticVariablesType.append( "\n" );

	QString classBody = QString( "%1%2%3%4%5" )
		.arg( classBegin )
		.arg( classStaticVariablesName )
		.arg( classStaticVariablesType )
		.arg( classConstructor )
		.arg( classGetSetMethods );

	QTextStream ts( &classFile );
	ts << classBody;

	classFile.close();	
}

QString CodeGenerator::convertToUppercase( QString& word )
{
	QString ret( word );

	//Get all characters of word
	for( int i = 0; i < word.size(); i++ )
	{
		QChar currentChar( word[i].toUpper() );

		ret[i] = currentChar;
	}

	return ret;
}

QString CodeGenerator::getConvertMethodByType( QString type )
{
	QString ret;

	//Check if type is a integer, wide character or boolean
	if( ATTRIBUTE_TYPE_SHORT == type ||
		ATTRIBUTE_TYPE_SHORT_INT == type ||
		ATTRIBUTE_TYPE_INT == type ||
		ATTRIBUTE_TYPE_LONG == type ||
		ATTRIBUTE_TYPE_LONG_INT == type ||
		ATTRIBUTE_TYPE_WIDE_CHARACTER == type )
	{
		ret = QString( "toInt" );
	}
	//Check if type is a unsigned integer
	if ( ATTRIBUTE_TYPE_USHORT == type ||
		 ATTRIBUTE_TYPE_UNSIGNED_SHORT == type ||
		 ATTRIBUTE_TYPE_UNSIGNED_SHORT_INT == type ||
		 ATTRIBUTE_TYPE_UINT == type ||
		 ATTRIBUTE_TYPE_UNSIGNED_INT == type || 
		 ATTRIBUTE_TYPE_ULONG == type ||
		 ATTRIBUTE_TYPE_UNSIGNED_LONG == type ||
		 ATTRIBUTE_TYPE_UNSIGNED_LONG_INT == type )
	{
		ret = QString( "toUInt" );
	}
	//Check if type is a string
	else if( ATTRIBUTE_TYPE_CHAR == type ||
			 ATTRIBUTE_TYPE_UCHAR == type ||
			 ATTRIBUTE_TYPE_UNSIGNED_CHAR == type ||
			 ATTRIBUTE_TYPE_STRING == type ||
			 ATTRIBUTE_TYPE_QSTRING == type )
	{
		ret = QString( "toString" );
	}
	//Check if type is a float
	else if ( ATTRIBUTE_TYPE_FLOAT == type )
	{
		ret = QString( "toFloat" );
	}
	//Check if type is a double
	else if( ATTRIBUTE_TYPE_DOUBLE == type ||
			 ATTRIBUTE_TYPE_LONG_DOUBLE == type )
	{
		ret = QString( "toDouble" );
	}
	else if ( ATTRIBUTE_TYPE_BOOL == type )
	{
		ret = QString( "toBool" );
	}
	//If type isn't any primitive type, so it's a compoite
	else
	{
		//TODO Error message: Tipo desconhecido
	}

	return ret;
}

QString CodeGenerator::buildPrimitiveGetSetSource( QString& className, QString& attributeName, QString& attributeType, QString& columnName )
{
	QString ret;

	//Check if attribute is a std::string
	if( ATTRIBUTE_TYPE_STRING == attributeType )
	{
		//Create get 
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( "std::" + attributeType + " " + className + "Bear::get" + attributeName + "()\n" )
			.arg( "{\n" )
			.arg( "    return m_object->getAttributePrimitiveValue( " + columnName + " )." + getConvertMethodByType( attributeType ) + "().toStdString();\n" )
			.arg( "}\n\n" );

		//Create set
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( "void " + className + "Bear::set" + attributeName + "( std::" + attributeType + " value )\n" )
			.arg( "{\n" )
			.arg( "    m_object->setAttributePrimitiveValue( " + columnName + ", QString::fromStdString( value ) );\n" )
			.arg( "}\n\n" );
	}
	//Check if attribute is a character
	else if ( ATTRIBUTE_TYPE_CHAR == attributeType ||
			  ATTRIBUTE_TYPE_UCHAR == attributeType ||
			  ATTRIBUTE_TYPE_UNSIGNED_CHAR == attributeType )
	{
		//Create get 
		ret = QString( "%1%2%3%4%5%6" )
			.arg( ret )
			.arg( attributeType + " " + className + "Bear::get" + attributeName + "()\n" )
			.arg( "{\n" )
			.arg( "    QString character = m_object->getAttributePrimitiveValue( " + columnName + " )." + getConvertMethodByType( attributeType ) + "();\n" )
			.arg( "    return character[0].toAscii();\n" )
			.arg( "}\n\n" );

		//Create set
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( "void " + className + "Bear::set" + attributeName + "( " + attributeType + " value )\n" )
			.arg( "{\n" )
			.arg( "    m_object->setAttributePrimitiveValue( " + columnName + ", QString( value ) );\n" )
			.arg( "}\n\n" );
	}
	//Check if attribute is a unsigned long
	else if ( ATTRIBUTE_TYPE_UNSIGNED_LONG == attributeType ||
			  ATTRIBUTE_TYPE_UNSIGNED_LONG_INT == attributeType ||
			  ATTRIBUTE_TYPE_ULONG == attributeType )
	{
		//Create get 
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( attributeType + " " + className + "Bear::get" + attributeName + "()\n" )
			.arg( "{\n" )
			.arg( "    return m_object->getAttributePrimitiveValue( " + columnName + " )." + getConvertMethodByType( attributeType ) + "();\n" )
			.arg( "}\n\n" );

		//Create set
		ret = QString( "%1%2%3%4%5%6" )
			.arg( ret )
			.arg( "void " + className + "Bear::set" + attributeName + "( " + attributeType + " value )\n" )
			.arg( "{\n" )
			.arg( "    qulonglong ulongValue = value;\n" )
			.arg( "    m_object->setAttributePrimitiveValue( " + columnName + ", ulongValue );\n" )
			.arg( "}\n\n" );
	}
	//Check if attribute is a long double
	else if ( ATTRIBUTE_TYPE_LONG_DOUBLE == attributeType )
	{
		//Create get 
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( attributeType + " " + className + "Bear::get" + attributeName + "()\n" )
			.arg( "{\n" )
			.arg( "    return m_object->getAttributePrimitiveValue( " + columnName + " )." + getConvertMethodByType( attributeType ) + "();\n" )
			.arg( "}\n\n" );

		//Create set
		ret = QString( "%1%2%3%4%5%6" )
			.arg( ret )
			.arg( "void " + className + "Bear::set" + attributeName + "( " + attributeType + " value )\n" )
			.arg( "{\n" )
			.arg( "    double longDoubleValue = value;\n" )
			.arg( "    m_object->setAttributePrimitiveValue( " + columnName + ", longDoubleValue );\n" )
			.arg( "}\n\n" );
	}
	//Case for all other types of attributes (int, float, double,...)
	else
	{
		//Create get 
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( attributeType + " " + className + "Bear::get" + attributeName + "()\n" )
			.arg( "{\n" )
			.arg( "    return m_object->getAttributePrimitiveValue( " + columnName + " )." + getConvertMethodByType( attributeType ) + "();\n" )
			.arg( "}\n\n" );

		//Create set
		ret = QString( "%1%2%3%4%5" )
			.arg( ret )
			.arg( "void " + className + "Bear::set" + attributeName + "( " + attributeType + " value )\n" )
			.arg( "{\n" )
			.arg( "    m_object->setAttributePrimitiveValue( " + columnName + ", value );\n" )
			.arg( "}\n\n" );
	}

	return ret;
}

QString CodeGenerator::buildPrimitiveGetSetHeader( QString& attributeName, QString& attributeType )
{
	QString ret;

	//Check if attribute is a std::string
	if( ATTRIBUTE_TYPE_STRING == attributeType )
	{
		//Create get 
		QString methodGet( "    std::" + attributeType + " get" + attributeName + "();\n" );

		//Create set
		QString methodSet( "    void set" + attributeName + "( std::" + attributeType + " value );\n\n" );

		ret = QString( "%1%2%3" )
			.arg( ret )
			.arg( methodGet )
			.arg( methodSet );
	}
	//Case for all other types of attributes (int, float, double,...)
	else
	{
		//Create get 
		QString methodGet( "    " + attributeType + " get" + attributeName + "();\n" );
		
		//Create set
		QString methodSet( "    void set" + attributeName + "( " + attributeType + " value );\n\n" );

		ret = QString( "%1%2%3" )
			.arg( ret )
			.arg( methodGet )
			.arg( methodSet );
	}

	return ret;
}
